//! Assembly Instruction

use anyhow::{Result, bail};
use command::{Comp, Dest, Jump};

use crate::label::LabelDictionary;

/// command instruction data;
mod command;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum Instruction {
    /// Address Instruction
    AInst(AInstruction),
    /// Computation Instruction
    CInst(CInstruction),
}

impl Instruction {
    /// Create a new address instruction
    fn new_address(code: &str) -> Result<Self> {
        AInstruction::new(code).map(Self::AInst)
    }
    fn new_computation(code: &str) -> Result<Self> {
        CInstruction::new(code).map(Self::CInst)
    }
    pub(super) fn new(code: &str) -> Result<Self> {
        if code.starts_with('@') {
            Self::new_address(code)
        } else {
            Self::new_computation(code)
        }
    }
    /// translate instruction to binary code
    ///
    /// # arguments
    /// - `dict`: label dictionary
    pub(super) fn to_binary(&self, dict: &mut LabelDictionary) -> Result<u16> {
        match self {
            Self::AInst(inst) => inst.to_binary(dict),
            Self::CInst(inst) => inst.to_binary(),
        }
    }
}

/// Address Instruction
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct AInstruction {
    /// memory address
    address: Option<u16>,
    /// the label will be used to generate the memory address
    label: Option<String>,
}

impl AInstruction {
    /// parse address instruction
    fn new(code: &str) -> Result<Self> {
        if code.starts_with('@') {
            let label = code[1..].trim();
            if let Ok(address) = label.parse::<u16>() {
                Ok(Self::new_address(address))
            } else {
                Ok(Self::new_label(label))
            }
        } else {
            bail!("Invalid address instruction {code}")
        }
    }
    fn new_label(label: &str) -> Self {
        Self {
            address: None,
            label: Some(label.to_string()),
        }
    }
    fn new_address(address: u16) -> Self {
        Self {
            address: Some(address),
            label: None,
        }
    }
    /// translate instruction to binary code
    ///
    /// # arguments
    /// - `dict`: label dictionary
    fn to_binary(&self, dict: &mut LabelDictionary) -> Result<u16> {
        if let Some(address) = self.address {
            Ok(address)
        } else {
            let label = self.label.as_deref().unwrap();
            Ok(dict.get(label))
        }
    }
}

/// Computation Instruction
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(super) struct CInstruction {
    /// destination register
    dest: Option<String>,
    /// computation operation
    comp: String,
    /// jump condition
    jump: Option<String>,
}

impl CInstruction {
    /// translate instruction to binary code
    fn to_binary(&self) -> Result<u16> {
        let jmp = Jump::bin(self.jump.as_deref())? as u16;
        let dest = Dest::bin(self.dest.as_deref())? as u16;
        let comp = Comp::bin(&self.comp)? as u16;
        Ok(0b111u16 << 13 | comp << 6 | dest << 3 | jmp)
    }
    /// parse a computation instruction from a string
    fn new(input: &str) -> Result<Self> {
        let input = input.replace(" ", "");
        let mut parts = input.split(';');
        let dest_and_comp: Vec<_> = parts.next().unwrap_or_default().split('=').collect();

        let jump = parts.next().map(|s| s.to_string());
        let cins = if dest_and_comp.len() == 2 {
            let dest = dest_and_comp[0].to_string();
            let comp = dest_and_comp[1].to_string();
            Self {
                dest: Some(dest),
                comp,
                jump,
            }
        } else {
            let comp = dest_and_comp[0].to_string();
            Self {
                dest: None,
                comp,
                jump,
            }
        };
        if cins.dest.is_none() && cins.jump.is_none() {
            bail!("Invalid C-instruction \"{input}\",must contain either a destination or a jump!");
        }
        Ok(cins)
    }
}

#[cfg(test)]
mod test;
